<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file contains the following classes:
 *
 * - Inflector
 * - Inflector_Exception
 * - Inflector_UnsupportedLanguageException
 *
 * PHP version 5
 *
 * This file is part of Majic - A rapid-development web application framework
 * for PHP 5.
 *
 * Majic is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * Majic is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Majic. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package     Inflector
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 * @copyright   2007-2008 Michael J. I. Jackson
 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL 3.0
 * @version     SVN: $Id: Inflector.php 36 2008-08-09 23:31:49Z mjijackson $
 */

/**
 * Contains functions useful in converting words from singular to plural form.
 * The default language pluralization rules that will be used are in English.
 * The {@link setLang()} function may be used to change this setting.
 *
 * @package     Inflector
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 */
class Inflector
{

    /**
     * Contains the inflector's pluralization rules.
     *
     * @var     array
     * @access  private
     */
    private $plural_rules = array();

    /**
     * Contains the inflector's uncountable rules.
     *
     * @var     array
     * @access  private
     */
    private $uncountables       = array();

    /**
     * The location of the directory containing the language rules.
     *
     * @var     string
     * @access  private
     */
    private $rules_dir;

    /**
     * Constructor.
     *
     * @access  private
     */
    private function __construct()
    {
        $this->rules_dir = dirname(__FILE__) . '/Inflector';
    }

    /**
     * Gets the instance of the Inflector class.
     *
     * @return  Inflector         The Inflector singleton object
     * @access  public
     * @static
     */
    public static function getInstance()
    {
        static $inst;
        if (!$inst) {
            $inst = new self();
        }
        return $inst;
    }

    /**
     * Sets the language rules the inflector will use. In order to use this
     * function, the appropriate rules file must be found in lang/rules.
     *
     * @param   string  $lang       The language to use
     * @return  void
     * @throws  Inflector_UnsupportedLanguageException  If the corresponding
     *                                                  language rules file
     *                                                  cannot be found
     * @access  public
     * @static
     */
    public static function setLang($lang)
    {
        $inst = self::getInstance();

        // clear old rules
        $inst->plural_rules = array();
        $inst->uncountables = array();

        $lang_file = $inst->rules_dir . '/' . strtolower(basename($lang)) . '.php';

        if ((@include_once $lang_file) === false) {
            throw new Inflector_UnsupportedLanguageException($lang);
        }
    }

    /**
     * Adds a plural rule to the inflector's internal array of plural rules.
     * May be used to add rules one at a time:
     *
     * <code>
     * Inflector::addPlural('/(quiz)$/i', '\1zes');
     * </code>
     *
     * or all at once:
     *
     * <code>
     * Inflector::addPlural(array(
     *     '/(quiz)$/i'            => '\1zes',
     *     '/(ss|sh|ch|x|z)$/i'    => '\1es',
     * ));
     * </code>
     *
     * @param   mixed   $rules          An array of rules ($re => $replace) or
     *                                  a regular expression string
     * @param   string  $replacement    If rules is a string, the replacement
     *                                  to use in case of a match
     * @return  void
     * @access  public
     * @static
     */
    public static function addPlural($rules, $replacement = '')
    {
        $inst = self::getInstance();

        if (!is_array($rules)) {
            $rules = array($rules => $replacement);
        }

        $inst->plural_rules = array_merge($rules, $inst->plural_rules);
    }

    /**
     * Adds an irregular plural rule to the inflector's internal array of
     * plural rules. May be used to add rules one at a time:
     *
     * <code>
     * Inflector::addIrregular('man', 'men');
     * </code>
     *
     * or all at once:
     *
     * <code>
     * Inflector::addIrregular(array(
     *     'man'       => 'men',
     *     'person'    => 'people'
     * ));
     * </code>
     *
     * @param   mixed   $rules          The singular form of the noun or an
     *                                  array of irregular nouns
     * @param   string  $plural         The plural form of the noun
     * @return  void
     * @access  public
     * @static
     */
    public static function addIrregular($rules, $plural = '')
    {
        if (!is_array($rules)) {
            $rules = array($rules => $plural);
        }
        foreach ($rules as $singular => $plural) {
            self::addPlural('/' . preg_quote($singular) . '$/', $plural);
        }
    }

    /**
     * Marks a word as uncountable, meaning that the plural form of the word
     * is the same as its singular form.
     *
     * @param   mixed   $word       The word (or an array of words) to mark as
     *                              uncountable
     * @return  void
     * @access  public
     * @static
     */
    public static function addUncountable($word)
    {
        $inst = self::getInstance();

        if (!is_array($word)) {
            $word = array($word);
        }
        foreach ($word as $w) {
            $inst->uncountables[] = strtolower(trim($w));
        }
    }

    /**
     * Converts a singular noun to its plural form.
     *
     * @param   string  $singular       The singular word
     * @return  string                  The word pluralized
     * @access  public
     * @static
     */
    public static function toPlural($singular)
    {
        $inst = self::getInstance();

        $singular = strtolower(trim($singular));
        $count = 0;

        if (in_array($singular, $inst->uncountables)) {
            return $singular;
        }

        foreach ($inst->plural_rules as $re => $replace) {
            $singular = preg_replace($re, $replace, $singular, 1, $count);
            if ($count) {
                return $singular;
            }
        }
    }

}

/**
 * Inflector exception. The base exception class for all Inflector exceptions.
 *
 * @package     Inflector
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 */
class Inflector_Exception extends Majic_Exception {}

/**
 * Unsupported language exception. Thrown when no appropriate inflection rules
 * can be found for the given language.
 *
 * @package     Inflector
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 */
class Inflector_UnsupportedLanguageException extends Inflector_Exception
{

    /**
     * Constructor.
     *
     * @param   string  $lang       The language that is not supported
     * @access  public
     */
    public function __construct($lang)
    {
        parent::__construct("Rules of inflection for language \"$lang\" cannot be found");
    }

}

// use English as the default language
Inflector::setLang('en');

?>
